package org.apereo.cas.config;

import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationPolicy;
import org.apereo.cas.authentication.ContextualAuthenticationPolicyFactory;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.adaptive.DefaultAdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.adaptive.geo.GeoLocationService;
import org.apereo.cas.authentication.policy.AllAuthenticationPolicy;
import org.apereo.cas.authentication.policy.AnyAuthenticationPolicy;
import org.apereo.cas.authentication.policy.GroovyScriptAuthenticationPolicy;
import org.apereo.cas.authentication.policy.NotPreventedAuthenticationPolicy;
import org.apereo.cas.authentication.policy.RequiredHandlerAuthenticationPolicy;
import org.apereo.cas.authentication.policy.RequiredHandlerAuthenticationPolicyFactory;
import org.apereo.cas.authentication.policy.RestfulAuthenticationPolicy;
import org.apereo.cas.authentication.policy.UniquePrincipalAuthenticationPolicy;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.core.authentication.AuthenticationPolicyProperties;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.client.RestTemplate;

/**
 * This is {@link CasCoreAuthenticationPolicyConfiguration}.
 * cas核心授权规则配置
 * @author Misagh Moayyed
 * @since 5.1.0
 */
@Configuration("casCoreAuthenticationPolicyConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@Slf4j
public class CasCoreAuthenticationPolicyConfiguration {

    //票据注册器
    @Autowired
    @Qualifier("ticketRegistry")
    private ObjectProvider<TicketRegistry> ticketRegistry;

    //是否启用谷歌地图
    @Autowired(required = false)
    @Qualifier("geoLocationService")
    private GeoLocationService geoLocationService;

    //spring的资源加载器
    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    private CasConfigurationProperties casProperties;
    //权限时间执行计划配置
    @ConditionalOnMissingBean(name = "authenticationPolicyExecutionPlanConfigurer")
    @Bean
    public AuthenticationEventExecutionPlanConfigurer authenticationPolicyExecutionPlanConfigurer() {
        return plan -> {
            final AuthenticationPolicyProperties police = casProperties.getAuthn().getPolicy();

            if (police.getReq().isEnabled()) {
                //仅当指定的处理程序成功验证其凭据时，才满足
                LOGGER.debug("Activating authentication policy [{}]", RequiredHandlerAuthenticationPolicy.class.getSimpleName());
                plan.registerAuthenticationPolicy(new RequiredHandlerAuthenticationPolicy(police.getReq().getHandlerName(), police.getReq().isTryAll()));
            } else if (police.getAll().isEnabled()) {
                //当且仅当所有给定的凭据都已成功验证时，才满足。对多个凭据的支持在CAS中是新的，此处理程序将只有在多因素身份验证的情况下才可接受。
                LOGGER.debug("Activating authentication policy [{}]", AllAuthenticationPolicy.class.getSimpleName());
                plan.registerAuthenticationPolicy(new AllAuthenticationPolicy());
            } else if (police.getNotPrevented().isEnabled()) {
                //如果仅当身份验证事件未被{@code proventedexception}阻止，则满足。
                LOGGER.debug("Activating authentication policy [{}]", NotPreventedAuthenticationPolicy.class.getSimpleName());
                plan.registerAuthenticationPolicy(notPreventedAuthenticationPolicy());
            } else if (police.getUniquePrincipal().isEnabled()) {
                //仅当主体尚未验证时满足并且没有与CAS的sso会话。否则，会阻止用户多次登录。请注意，此策略根据CAS的需要，给票证存储/注册表增加了额外的负担
                //查询所有在登记处找到的相关票证进行交叉核对具有现有票证的请求用户名。
                LOGGER.debug("Activating authentication policy [{}]", UniquePrincipalAuthenticationPolicy.class.getSimpleName());
                plan.registerAuthenticationPolicy(new UniquePrincipalAuthenticationPolicy(ticketRegistry.getIfAvailable()));
            } else if (!police.getGroovy().isEmpty()) {
                //是够启用Groovy脚本
                LOGGER.debug("Activating authentication policy [{}]", GroovyScriptAuthenticationPolicy.class.getSimpleName());
                police.getGroovy().forEach(groovy -> plan.registerAuthenticationPolicy(new GroovyScriptAuthenticationPolicy(resourceLoader, groovy.getScript())));
            } else if (!police.getRest().isEmpty()) {
                //执行rest endpoint 去发现授权规则
                LOGGER.debug("Activating authentication policy [{}]", RestfulAuthenticationPolicy.class.getSimpleName());
                police.getRest().forEach(r -> plan.registerAuthenticationPolicy(new RestfulAuthenticationPolicy(new RestTemplate(), r.getEndpoint())));
            } else if (police.getAny().isEnabled()) {
                //如果任何身份验证处理程序成功，则满足。允许选项避免短路，并尝试每个处理程序，即使之前的一个处理程序成功。
                LOGGER.debug("Activating authentication policy [{}]", AnyAuthenticationPolicy.class.getSimpleName());
                plan.registerAuthenticationPolicy(new AnyAuthenticationPolicy(police.getAny().isTryAll()));
            }
        };
    }

    @Bean
    public AuthenticationPolicy notPreventedAuthenticationPolicy() {
        return new NotPreventedAuthenticationPolicy();
    }

    //自适应认证政策
    @ConditionalOnMissingBean(name = "adaptiveAuthenticationPolicy")
    @Bean
    @RefreshScope
    public AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy() {
        return new DefaultAdaptiveAuthenticationPolicy(this.geoLocationService, casProperties.getAuthn().getAdaptive());
    }
    //必需的处理程序身份验证策略工厂
    @ConditionalOnMissingBean(name = "requiredHandlerAuthenticationPolicyFactory")
    @Bean
    public ContextualAuthenticationPolicyFactory requiredHandlerAuthenticationPolicyFactory() {
        return new RequiredHandlerAuthenticationPolicyFactory();
    }

}
